package ssllink

import (
	"fmt"
	"net"
	"os/exec"

	"strings"
	"time"

	"github.com/vishvananda/netlink"
)

var (
	localInterface netlink.Link
	iface          netlink.Link
)

func (sl *Sslink) ConfigInterface(cSess *ConnSession) error {
	var err error
	iface, err = netlink.LinkByName(cSess.TunName)
	if err != nil {
		return err
	}
	// ip address
	_ = netlink.LinkSetUp(iface)
	_ = netlink.LinkSetMulticastOff(iface)

	addr, _ := netlink.ParseAddr(IpMask2CIDR(cSess.VPNAddress, cSess.VPNMask))
	err = netlink.AddrAdd(iface, addr)

	return err
}

func (sl *Sslink) SetRoutes(cSess *ConnSession) error {
	// routes
	dst, _ := netlink.ParseIPNet(cSess.ServerAddress + "/32")
	gateway := net.ParseIP(sl.config.Interface.Ip4)

	ifaceIndex := iface.Attrs().Index
	localInterfaceIndex := localInterface.Attrs().Index

	route := netlink.Route{LinkIndex: localInterfaceIndex, Dst: dst, Gw: gateway}
	err := netlink.RouteAdd(&route)
	if err != nil {
		if !strings.HasSuffix(err.Error(), "exists") {
			return sl.routingError(dst, err)
		}
	}

	// 如果包含路由为空必为全局路由，如果使用包含域名，则包含路由必须填写一个，如 dns 地址
	if len(cSess.SplitInclude) == 0 {
		cSess.SplitInclude = append(cSess.SplitInclude, "0.0.0.0/0.0.0.0")

		// 全局模式，重置默认路由优先级，如 OpenWrt 默认优先级为 0
		zero, _ := netlink.ParseIPNet("0.0.0.0/0")
		sl.delAllRoute(&netlink.Route{LinkIndex: localInterfaceIndex, Dst: zero})
		_ = netlink.RouteAdd(&netlink.Route{LinkIndex: localInterfaceIndex, Dst: zero, Gw: gateway, Priority: 10})
	}

	// 如果使用域名包含，原则上不支持在顶级域名匹配中排除某个具体域名的 IP
	for _, ipMask := range cSess.SplitInclude {
		dst, _ = netlink.ParseIPNet(IpMaskToCIDR(ipMask))
		route = netlink.Route{LinkIndex: ifaceIndex, Dst: dst, Priority: 6}
		err = netlink.RouteAdd(&route)
		if err != nil {
			if !strings.HasSuffix(err.Error(), "exists") {
				return sl.routingError(dst, err)
			}
		}
	}

	// 支持在 SplitInclude 网段中排除某个路由
	if len(cSess.SplitExclude) > 0 {
		for _, ipMask := range cSess.SplitExclude {
			dst, _ = netlink.ParseIPNet(IpMaskToCIDR(ipMask))
			route = netlink.Route{LinkIndex: localInterfaceIndex, Dst: dst, Gw: gateway, Priority: 5}
			err = netlink.RouteAdd(&route)
			if err != nil {
				if !strings.HasSuffix(err.Error(), "exists") {
					return sl.routingError(dst, err)
				}
			}
		}
	}

	if len(cSess.DNS) > 0 {
		sl.setDNS(cSess)
	}

	return nil
}

func (sl *Sslink) ResetRoutes(cSess *ConnSession) {
	// routes
	localInterfaceIndex := localInterface.Attrs().Index

	for _, ipMask := range cSess.SplitInclude {
		if ipMask == "0.0.0.0/0.0.0.0" {
			// 重置默认路由优先级
			zero, _ := netlink.ParseIPNet("0.0.0.0/0")
			gateway := net.ParseIP(sl.config.Interface.Ip4)
			_ = netlink.RouteDel(&netlink.Route{LinkIndex: localInterfaceIndex, Dst: zero})
			_ = netlink.RouteAdd(&netlink.Route{LinkIndex: localInterfaceIndex, Dst: zero, Gw: gateway})
			break
		}
	}

	dst, _ := netlink.ParseIPNet(cSess.ServerAddress + "/32")
	_ = netlink.RouteDel(&netlink.Route{LinkIndex: localInterfaceIndex, Dst: dst})

	if len(cSess.SplitExclude) > 0 {
		for _, ipMask := range cSess.SplitExclude {
			dst, _ = netlink.ParseIPNet(IpMaskToCIDR(ipMask))
			_ = netlink.RouteDel(&netlink.Route{LinkIndex: localInterfaceIndex, Dst: dst})
		}
	}

	if len(cSess.DynamicSplitExcludeDomains) > 0 {
		cSess.DynamicSplitExcludeResolved.Range(func(_, value any) bool {
			ips := value.([]string)
			for _, ip := range ips {
				dst, _ = netlink.ParseIPNet(ip + "/32")
				_ = netlink.RouteDel(&netlink.Route{LinkIndex: localInterfaceIndex, Dst: dst})
			}

			return true
		})
	}

	if len(cSess.DNS) > 0 {
		restoreDNS(cSess)
	}
}

func (sl *Sslink) DynamicAddIncludeRoutes(ips []string) {
	ifaceIndex := iface.Attrs().Index

	for _, ip := range ips {
		dst, _ := netlink.ParseIPNet(ip + "/32")
		route := netlink.Route{LinkIndex: ifaceIndex, Dst: dst, Priority: 6}
		_ = netlink.RouteAdd(&route)
	}
}

func (sl *Sslink) DynamicAddExcludeRoutes(ips []string) {
	localInterfaceIndex := localInterface.Attrs().Index
	gateway := net.ParseIP(sl.config.Interface.Ip4)

	for _, ip := range ips {
		dst, _ := netlink.ParseIPNet(ip + "/32")
		route := netlink.Route{LinkIndex: localInterfaceIndex, Dst: dst, Gw: gateway, Priority: 5}
		_ = netlink.RouteAdd(&route)
	}
}

func (sl *Sslink) GetLocalInterface() error {

	// just for default route
	routes, err := netlink.RouteGet(net.ParseIP("8.8.8.8"))
	if len(routes) > 0 {
		route := routes[0]
		localInterface, err = netlink.LinkByIndex(route.LinkIndex)
		if err != nil {
			return err
		}

		sl.config.Interface.Name = localInterface.Attrs().Name
		sl.config.Interface.Ip4 = route.Src.String()
		sl.config.Interface.Gateway = route.Gw.String()
		sl.config.Interface.Mac = localInterface.Attrs().HardwareAddr.String()
		return nil
	}
	return err
}

func (sl *Sslink) delAllRoute(route *netlink.Route) {
	err := netlink.RouteDel(route)
	if err != nil {
		return
	}
	sl.delAllRoute(route)
}

func (sl *Sslink) routingError(dst *net.IPNet, err error) error {
	return fmt.Errorf("routing error: %s %s", dst.String(), err)
}

func (sl *Sslink) setDNS(cSess *ConnSession) {
	// dns
	if len(cSess.DNS) > 0 {
		// 使用动态域名路由时 DNS 一定走 VPN 才能进行流量分析
		if len(cSess.DynamicSplitIncludeDomains) > 0 {
			sl.DynamicAddIncludeRoutes(cSess.DNS)
		}

		// 部分云服务器会在设置路由时重写 /etc/resolv.conf，延迟两秒再设置
		go func() {
			CopyFile("/tmp/resolv.conf.bak", "/etc/resolv.conf")

			var dnsString string
			for _, dns := range cSess.DNS {
				dnsString += fmt.Sprintf("nameserver %s\n", dns)
			}
			time.Sleep(2 * time.Second)
			// OpenWrt 会将 127.0.0.1 写在最下面，影响其上面的解析
			err := NewRecord("/etc/resolv.conf").Write(dnsString, false)
			if err != nil {
				return
			}
		}()
	}
}

func restoreDNS(cSess *ConnSession) {
	// dns
	// 软件崩溃会导致无法恢复 resolv.conf 从而无法上网，需要重启系统
	if len(cSess.DNS) > 0 {
		CopyFile("/etc/resolv.conf", "/tmp/resolv.conf.bak")
	}
}

func execCmd(cmdStrs []string) error {
	for _, cmdStr := range cmdStrs {
		cmd := exec.Command("sh", "-c", cmdStr)
		stdoutStderr, err := cmd.CombinedOutput()
		if err != nil {
			return fmt.Errorf("%s %s %s", err, cmd.String(), string(stdoutStderr))
		}
	}
	return nil
}
